package com.dx.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dx.mapper.TaskMapper;
import com.dx.pojo.Task;
import com.dx.service.MailService;
import com.dx.service.TaskService;
import com.dx.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @program: Isp_Automatic_punch
 * @description:
 * @author: hanabi
 * @create: 2022-02-09 22:07
 **/
@Service
@Slf4j
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private MailService mailService;
    private String url = "https://xsswzx.cdu.edu.cn/ispstu/com_user/weblogin.asp";

    @Override
    public synchronized void processTask(Task task) throws IOException {
        ChromeDriverService service = new ChromeDriverService.Builder().usingDriverExecutable(new File("/usr/local/bin/chromedriver")).usingAnyFreePort().build();
        service.start();
        //静默自动化登记
        ChromeOptions options = new ChromeOptions();
        options.addArguments("headless");
        options.addArguments("--disable-gpu");
        options.addArguments("--no-sandbox");
        options.addArguments("--disable-dev-shm-usage");
        options.addArguments("--disable-extensions"); // disabling extensions
        options.addArguments("--disable-gpu");
        options.addArguments("hide-scrollbars");
        ChromeDriver driver = new ChromeDriver(options);
        //窗口最大化
        driver.manage().window().maximize();
        int remainCount = 2 - task.getFailedCount();
        try {
            driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
            driver.get(url);
            WebElement form = null;
            try {
                form = driver.findElement(By.id("提交"));
            } catch (UnhandledAlertException e) {
                if (e.getMessage().contains("系统即将关闭")) {
                    driver.switchTo().alert().accept();
                }
                form = driver.findElement(By.id("提交"));
            }
            driver.findElementById("username").sendKeys(task.getUsername());
            driver.findElementById("userpwd").sendKeys(task.getPassword());
            String code_val = driver.findElementByCssSelector("#form > div:nth-child(3)").getText().trim();
            driver.findElementById("code").sendKeys(code_val);
            log.info("==>验证码" + code_val);
            form.click();
            Thread.sleep(500);
            try {
                driver.findElementByCssSelector("body > div.all-wrapper.with-side-panel.solid-bg-all > div.onboarding-modal.modal.fade.animated.show-on-load > div > div > button").click();
            } catch (NoSuchElementException e) {
                e.printStackTrace();
            }
            //隐藏元素直接通过原生js触发点击事件
            driver.executeScript("document.querySelector(\"body > div.all-wrapper.with-side-panel.solid-bg-all > div.layout-w > div.menu-w.color-scheme-light.color-style-transparent.menu-position-side.menu-side-left.menu-layout-compact.sub-menu-style-over.sub-menu-color-bright.selected-menu-color-light.menu-activated-on-hover.menu-has-selected-link > ul > li:nth-child(13) > div > div.sub-menu-i > ul > li > a\").click();");
            driver.switchTo().frame(driver.findElementById("myiframe"));
            driver.findElementByXPath("/html/body/div/div[1]/div/div/table/tbody/tr[3]/td/a[1]/input").click();
            driver.findElementByCssSelector("#province").sendKeys(task.getProvince());
            driver.findElementByCssSelector("#city").sendKeys(task.getCity());
            driver.findElementByCssSelector("#area").sendKeys(task.getArea());
            driver.findElementByXPath("/html/body/form/div/div/div/div[8]/div/div/div/div/button[1]").click();
            try {
                driver.switchTo().alert().accept();
            } catch (NoAlertPresentException e) {
                log.info("==>学号：" + task.getUsername() + "==>打卡中出现异常信息：" + e.getStackTrace().toString());
            }
            String registerInfo = getRegisterInfo(driver);
            String patten = "分钟内可删除  删除\n" + DateUtil.getFormatDate(System.currentTimeMillis());
            if (registerInfo.trim().contains(patten.trim())) {
                setAsSuccess(task);
                log.info("==>学号：" + task.getUsername() + "==>打卡成功");
                mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡成功\n当前登记信息记录：\n" + registerInfo);
            } else {
                log.info("==>学号：" + task.getUsername() + "==>打卡异常：字符串不匹配");
                addFailedCount(task);
                mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡已提交但打卡记录匹配失败，请在邮件中查看当前打卡记录中是否存在今日的登记信息。如已打卡可忽略此次通知。剩余补偿打卡次数：" + remainCount + "\n当前登记信息记录：\n" + registerInfo);
            }
        } catch (UnhandledAlertException e) {
            String registerInfo = getRegisterInfo(driver);
            log.info("学号：" + task.getUsername() + "==>打卡失败，出现预料之外的弹窗");
            if (e.getMessage().contains(DateUtil.getFormatDate(System.currentTimeMillis()) + "登记已存在") || registerInfo.contains("分钟内可删除并重新登记")) {
                mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡记录已存在！系统今日将不再补偿打卡\n当前登记信息记录：\n" + registerInfo);
                setAsSuccess(task);
            } else if (e.getMessage().contains("ISP检测到登录学号为非在校学生") || e.getMessage().contains("学号或密码错误，请重新登陆")) {
                mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>学号或密码有误！系统今日将不再补偿打卡！如需打卡，请在今天之内前往：http://meetyouat.icu:8080 更新个人信息\n当前登记信息记录：\n" + registerInfo);
                setAsFail(task);
            } else {
                addFailedCount(task);
                if (remainCount < 1) {
                    mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡失败\n弹窗消息：" + e.getAlertText() + "\n请检查密码、地区名称是否有误或打卡记录已存在，如已打卡可忽略此次通知。当前补偿打卡次数已耗尽，今日不再补偿打卡！若需要重置补偿次数请检查并更新信息或手动打卡！" + "\n当前登记信息记录：\n" + registerInfo);
                } else {
                    mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡失败\n弹窗消息：" + e.getAlertText() + "\n请检查密码、地区名称是否有误或打卡记录已存在，如已打卡可忽略此次通知。剩余补偿打卡次数：" + remainCount + "\n当前登记信息记录：\n" + registerInfo);
                }
            }
        } catch (NoSuchElementException e) {
            log.info("学号：" + task.getUsername() + "==>打卡失败，无法获取页面元素，请检查isp页面结构是否发生变化");
            e.printStackTrace();
            mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡失败，可能原因：ISP服务器正处于停机维护或服务拥堵。此次打卡不消耗补偿次数，当前剩余补偿次数为：" + (remainCount + 1));
        } catch (Exception e) {
            e.printStackTrace();
            log.info("学号：" + task.getUsername() + "==>打卡失败，未知错误");
            mailService.sendMail(task.getEmail(), "ISP-打卡结果通知", "学号：" + task.getUsername() + "==>打卡失败，可能原因：服务器拥挤或未知错误，此次打卡不消耗补偿次数，当前剩余补偿次数为：" + (remainCount + 1));
        } finally {
            log.info("==>本次打卡任务单元结束");
            driver.quit();
            service.stop();
        }
    }

    private void setAsFail(Task task) {
        task.setFailedCount(3);
        updateByUsername(task);
    }

    private String getRegisterInfo(ChromeDriver driver) {
        try {
            String text = driver.findElementByCssSelector("body > div > div:nth-child(4) > div > div:nth-child(3)").getText();
            return StringUtils.hasLength(text) ? text : "获取当前登记信息失败";
        } catch (UnhandledAlertException e) {
            return e.getMessage();
        } catch (Exception e) {
            return "获取当前登记信息失败";
        }
    }

    private void setAsSuccess(Task task) {
        task.setIsSuc(1);
        updateByUsername(task);
    }

    private void addFailedCount(Task task) {
        task.setFailedCount(task.getFailedCount() + 1);
        updateByUsername(task);
    }

    @Override
    public Task selectByUsername(Task task) {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getUsername, task.getUsername());
        return taskMapper.selectOne(wrapper);
    }

    @Override
    public boolean removeByUsername(String username) throws Exception {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getUsername, username);
        int delete = taskMapper.delete(wrapper);
        return delete == 1;
    }

    @Override
    public void updateByUsername(Task task) {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getUsername, task.getUsername());
        String addr = task.getProvince() + task.getCity() + task.getArea();
        task.setAddr(addr);
        taskMapper.update(task, wrapper);
    }

    @Override
    public void resetAll() {
        taskMapper.resetAll();
    }

    @Override
    public List<Task> selectAll() {
        log.info("querying all the unfinished tasks...");
        return taskMapper.selectList(null);
    }


    @Override
    public void punchInOneClick() {
        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Task::getIsSuc, 0).lt(Task::getFailedCount, 3);
        List<Task> tasks = Optional.ofNullable(taskMapper.selectList(wrapper)).orElse(new ArrayList<>());
        tasks.stream().forEach(tsk -> {
            try {
                processTask(tsk);
                //减缓频率，避免邮件因此发送失败
                Thread.sleep(1000 * 30);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("一键打卡中学号为==>" + tsk.getUsername() + "打卡异常终止...");
            }
        });
    }

    @Override
    public void add(Task task) {
        String addr = task.getProvince() + task.getCity() + task.getArea();
        task.setIsSuc(0);
        task.setAddr(addr);
        taskMapper.insert(task);
    }
}
